/*
 * AIEngine a new generation network intrusion detection system.
 *
 * Copyright (C) 2013-2023  Luis Campo Giralte
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 * Written by Luis Campo Giralte <luis.camp0.2009@gmail.com>
 *
 */
#ifndef SRC_HTTPSESSION_H_
#define SRC_HTTPSESSION_H_

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio.hpp>
#if !defined(IS_DARWIN)
#include <filesystem>
#else
#include <boost/filesystem.hpp>
#endif
#include <glob.h>
#include "Pointer.h"
#include "json.hpp"
#include "NetworkStack.h"
#include "PacketDispatcher.h"
#include "System.h"
#include "HTTPUris.h"

using tcp = boost::asio::ip::tcp;       // from <boost/asio.hpp>
namespace bhttp = boost::beast::http;    // from <boost/beast/http.hpp>

namespace aiengine {

/* LCOV_EXCL_START */

class PacketDispatcher;

typedef std::unordered_map<std::string, std::function<void(bhttp::response<bhttp::dynamic_body>&)>> HttpOperations;

class HTTPSession : public std::enable_shared_from_this<HTTPSession>
{
public:
	static const int script_delay_ok = 5000;
	static const int script_delay_error = 3000;
	std::string_view datapath = "/tmp/aiengine-private";

        explicit HTTPSession(tcp::socket socket, const SharedPointer<NetworkStack> &stack, PacketDispatcher *pdis):
                send_response(*this),
                socket_(std::move(socket)),
		stack_(stack),
		pdis_(pdis),
		get_operations_ {
			{HTTPUris::show_protocols_summary.data(), std::bind(&HTTPSession::show_protocols_summary, this, std::placeholders::_1)}
			,{HTTPUris::show_network_flows.data(), std::bind(&HTTPSession::show_network_flows, this, std::placeholders::_1)}
			,{HTTPUris::show_summary.data(), std::bind(&HTTPSession::show_summary, this, std::placeholders::_1)}
			,{HTTPUris::show_system.data(), std::bind(&HTTPSession::show_system, this, std::placeholders::_1)}
			,{HTTPUris::pcapfile.data(), std::bind(&HTTPSession::show_pcapfile, this, std::placeholders::_1)}
			,{HTTPUris::show_network_flow.data(), std::bind(&HTTPSession::show_network_flow, this, std::placeholders::_1)}
			,{HTTPUris::show_current_packet.data(), std::bind(&HTTPSession::show_current_packet, this, std::placeholders::_1)}
			,{HTTPUris::show_logs.data(), std::bind(&HTTPSession::show_logs, this, std::placeholders::_1)}
			,{HTTPUris::show_health.data(), std::bind(&HTTPSession::show_health, this, std::placeholders::_1)}
			,{std::string(HTTPUris::show_evidences.data()) + "." + std::to_string(getpid()) + ".pcap",
				std::bind(&HTTPSession::show_evidences, this, std::placeholders::_1)}
#if defined(PYTHON_BINDING)
			,{HTTPUris::show_anomalies.data(), std::bind(&HTTPSession::show_anomalies, this, std::placeholders::_1)}
			,{HTTPUris::python_script.data(), std::bind(&HTTPSession::show_python_script, this, std::placeholders::_1)}
			,{HTTPUris::show_python_locals.data(), std::bind(&HTTPSession::show_python_locals, this, std::placeholders::_1)}
			,{HTTPUris::show_python_help.data(), std::bind(&HTTPSession::show_python_help, this, std::placeholders::_1)}
			,{HTTPUris::show_python_code.data(), std::bind(&HTTPSession::show_python_code, this, std::placeholders::_1)}
#endif
			,{HTTPUris::show_protocol.data(), std::bind(&HTTPSession::show_protocol_summary, this, std::placeholders::_1)}
			},
		post_operations_ {
			{HTTPUris::pcapfile.data(), std::bind(&HTTPSession::upload_pcapfile, this, std::placeholders::_1)}
#if defined(PYTHON_BINDING)
			,{HTTPUris::python_script.data(), std::bind(&HTTPSession::upload_python_script, this, std::placeholders::_1)}
			,{HTTPUris::alarms.data(), std::bind(&HTTPSession::manage_alarms, this, std::placeholders::_1)}
#endif
			},
		put_operations_ {
			{HTTPUris::show_network_flow.data(), std::bind(&HTTPSession::update_network_flow, this, std::placeholders::_1)}
#if defined(PYTHON_BINDING)
			,{HTTPUris::show_python_locals.data(), std::bind(&HTTPSession::set_python_object_property, this, std::placeholders::_1)}
#endif
			}
                {}

        virtual ~HTTPSession() { socket_.close(); }

        void start();

        int32_t getTotalRequests() const { return total_requests_; }
        int32_t getTotalResponses() const { return total_responses_; }
        int64_t getTotalBytes() const { return total_bytes_; }
        std::time_t getRequestTime() const { return request_time_; }

        // This is the C++11 equivalent of a generic lambda.
        // The function object is used to send an HTTP response message.
        struct send_lambda {
                HTTPSession& self_;

                explicit send_lambda(HTTPSession& self) : self_(self) {}

                template<bool isRequest, class Body, class Fields>
                void operator()(bhttp::message<isRequest, Body, Fields>&& msg) const {
                        // The lifetime of the message has to extend
                        // for the duration of the async operation so
                        // we use a shared_ptr to manage it.
                        auto sp = std::make_shared<
                                bhttp::message<isRequest, Body, Fields>>(std::move(msg));

                        // Store a type-erased version of the shared
                        // pointer in the class to keep it alive.
                        self_.res_ = sp;

                        bhttp::async_write(self_.socket_, *sp,
                        	std::bind(
                                	&HTTPSession::handle_write,
                                        self_.shared_from_this(),
                                        std::placeholders::_1,
                                        std::placeholders::_2,
                                        sp->need_eof()));
                }
	};

private:
	SharedPointer<Flow> get_flow_from_uri(const std::string &uri) const;

        void handle_write(boost::system::error_code ec, std::size_t bytes_transferred, bool close);
        void handle_read(boost::system::error_code ec, std::size_t bytes_transferred);
        void do_close();
        void do_read();
	void process_request(bhttp::request<bhttp::dynamic_body>&& req);
	bool check_operations(bhttp::response<bhttp::dynamic_body> &response, const HttpOperations &operations);

	void handle_get_message(bhttp::response<bhttp::dynamic_body> &response);
	void show_protocols_summary(bhttp::response<bhttp::dynamic_body> &response);
	void show_protocol_summary(bhttp::response<bhttp::dynamic_body> &response);
	void show_network_flows(bhttp::response<bhttp::dynamic_body> &response);
	void show_summary(bhttp::response<bhttp::dynamic_body> &response);
	void show_system(bhttp::response<bhttp::dynamic_body> &response);
	void show_uris(bhttp::response<bhttp::dynamic_body> &response);
	void show_pcapfile(bhttp::response<bhttp::dynamic_body> &response);
	void show_network_flow(bhttp::response<bhttp::dynamic_body> &response);
	void show_current_packet(bhttp::response<bhttp::dynamic_body> &response);
	void show_logs(bhttp::response<bhttp::dynamic_body> &response);
	void show_health(bhttp::response<bhttp::dynamic_body> &response);
	void show_evidences(bhttp::response<bhttp::dynamic_body> &response);
#if defined(PYTHON_BINDING)
	void show_anomalies(bhttp::response<bhttp::dynamic_body> &response);
	void show_python_script(bhttp::response<bhttp::dynamic_body> &response);
	void show_python_locals(bhttp::response<bhttp::dynamic_body> &response);
	void show_python_help(bhttp::response<bhttp::dynamic_body> &response);
	void show_python_code(bhttp::response<bhttp::dynamic_body> &response);
#endif
	void parse_arguments(const std::string &data, std::map<std::string, std::string> &arguments) const;

	void handle_post_message(bhttp::response<bhttp::dynamic_body> &response);
	void upload_pcapfile(bhttp::response<bhttp::dynamic_body> &response);
#if defined(PYTHON_BINDING)
	void manage_alarms(bhttp::response<bhttp::dynamic_body> &response);
	void upload_python_script(bhttp::response<bhttp::dynamic_body> &response);
	void set_python_object_property(bhttp::response<bhttp::dynamic_body> &response);
	bool run_python_code(const std::string &filename, const std::string &code) const;
	bool is_authorized(bhttp::response<bhttp::dynamic_body> &response) const;
#endif
	std::string generate_redirection_url(bhttp::request<bhttp::dynamic_body>& req) const;
	void handle_put_message(bhttp::response<bhttp::dynamic_body> &response);
	void update_network_flow(bhttp::response<bhttp::dynamic_body> &response);

	// Variables
	bhttp::request<bhttp::dynamic_body> request;
	std::string ipsrc_ = "";
	int portsrc_ = 0;
        send_lambda send_response;
        int32_t total_requests_ = 0;
        int32_t total_responses_ = 0;
        int64_t total_bytes_ = 0;
        std::time_t request_time_ = (std::time_t)0;
        tcp::socket socket_;
        boost::beast::flat_buffer buffer_;
        std::shared_ptr<void> res_;
	SharedPointer<NetworkStack> stack_ = nullptr;
	PacketDispatcher *pdis_ = nullptr;
	const HttpOperations get_operations_;
	const HttpOperations post_operations_;
	const HttpOperations put_operations_;
};

/* LCOV_EXCL_STOP */

} // namespace aiengine

#endif // SRC_HTTPSESSION_H_
